using System;
using System.Collections;
using System.Threading;
 
namespace RSSRadio.Model
{
    /// <summary>
    /// This enum defines the states of a subscription. 
    /// NotLoaded is the initial state. 
    /// Once load of episodes is scheduled the subscription goes to a state of LoadPending. 
    /// When request to server is complete and all episodes are loaded the subscription goes in to EpisodesLoaded 
    /// If there is an error when requesting episodes the feed goes from LoadPending into Error
    /// </summary>
    public enum FeedState 
    {
        Error, NotLoaded, LoadPending, EpisodesLoaded 
    }




    /// <summary>
    /// Holds information about a subscription and provides methods specific to the subscription including loading of episodes
    /// </summary>
    public class Subscription: SubscriptionUpdateListenerReplicatorImpl
    {
        /// <summary>
        /// feed URL. This is the RSS URL. This field is used as a primary identifier of the podcast
        /// </summary>
        public String URL;

        /// <summary>
        /// feed title
        /// </summary>
        public string Title;

        public int RetnetionDays=10;

        public string lastDownloadedEpisodeUrl;

        public string lastDownloadedEpisodePublishDate;


        /// <summary>
        /// Array of feed episodes. This is not persistent it is loaded each time.
        /// </summary>
        [System.Xml.Serialization.XmlIgnoreAttribute]
        public EpisodeDetails[] episodes;

        /// <summary>
        /// Feed state. This is used to track the feed state
        /// </summary>
        [System.Xml.Serialization.XmlIgnoreAttribute]
        public FeedState state = FeedState.NotLoaded;



        /// <summary>
        /// This method allows setting of feed state in controlled fashion. 
        /// A check is made to not have two threads attempt episodes load at the same time
        /// </summary>
        /// <param name="state"></param>
        private void setFeedState( FeedState state)
        {
            lock (this)
            {
                if (this.state == FeedState.LoadPending & state == FeedState.LoadPending)
                {
                    //Cannot allow two or more threads to attempt episodes load
                    throw new System.MemberAccessException();

                }
                FeedState oldState = this.state;
                this.state = state;

                this.SubscriptionStateUpdated(this.URL, oldState, this.state);
            }
        }


        /// <summary>
        /// Loads feed episodes asynchronously
        /// </summary>
        public void loadFeedAsync()
        {
            Globals.ThreadPool.QueueUserWorkItem(new WaitCallback(this.loadFeedAsyncWorker));
        }

        /// <summary>
        /// Worker for loading feeds asynchronously
        /// </summary>
        /// <param name="o"></param>
        private void loadFeedAsyncWorker(object o)
        {
            try
            {
                loadFeed();
            }
            catch (Exception)
            {
            }
        }


        /// <summary>
        /// This method loads a RSS for a podcast feed. It uses Rss module to parse the feed episodes and 
        /// initialize the episodes member. Uon entering this method feed state is changedRetentionOnly to LoadPending.
        /// Following success state is set to EpisodesLoaded. On error the state is set to Error.
        /// 
        /// The mothod is affecting the state on entry and exit and is protected against execution by multiple 
        /// threads simultaneously.
        /// 
        /// Method can be called externally to perform synchronous load. IF async operation is needed the <see cref="loadFeedAsync"/> method is to be used
        /// </summary>
        public void loadFeed()
        {
            //entering LoadPending can raise concurency exception.
            this.setFeedState(FeedState.LoadPending);
            try
            {
                Rss.RssChannel myRssChannel = null;

                Rss.RssFeed feed = Rss.RssFeed.Read(this.URL);
                if (feed != null && feed.Channels.Count > 0)
                    myRssChannel = feed.Channels[0];
                else
                    throw new ArgumentException("Invalid feed URL. Cannot locate channels");

                if (this.Title != myRssChannel.Title)
                {
                    //title has changedRetentionOnly
                    this.Title = myRssChannel.Title;
                    this.SubscriptionDetailsUpdated(this.URL);
                }

                ArrayList episodes = new ArrayList();

                foreach (Rss.RssItem item in myRssChannel.Items)
                {
                    EpisodeDetails episode;

                    if (item.Enclosure != null) // && item.Enclosure.Type.Equals("audio/mpeg"))
                        episode = new EpisodeDetails(StringUtil.StripXmlCDATATags(item.Title), item.Description, item.PubDate.ToString(), item.Enclosure.Url);
                    else
                        episode = new EpisodeDetails(StringUtil.StripXmlCDATATags(item.Title), item.Description, item.PubDate.ToString(), item.Link);

                    // ignore podcasts with broken urls
                    //       (gopher://rss-net.sf.net/ is returned by RSS.Net on error)
                    if (episode.Url.AbsoluteUri != "gopher://rss-net.sf.net/")
                        episodes.Add(episode);
                }

                if (episodes.Count == 0)
                    this.episodes = null;
                else
                {
                    // copy the arraylist to an array so we can return it
                    EpisodeDetails[] episodesArray = new EpisodeDetails[episodes.Count];
                    int i = 0;
                    foreach (EpisodeDetails ed in episodes)
                    {
                        episodesArray[i] = ed;
                        i++;
                    }

                    this.episodes = episodesArray;
                }
 


                //Update state
                setFeedState(FeedState.EpisodesLoaded);
            }
            catch (Exception e)
            {
                setFeedState( FeedState.Error);
                throw e;
            }
        }


    }
}